var FIXED_STEP_IDEAL_DURATION_MS = 1000/25; //25 (game state) updates per second

/**
 * @public
 * @constructor
 * @param{Array} Array of GameEntity objects
 */
function GameLoop(gameEntities, inputQueue) {
	
	window.requestAnimationFrame = 
			window.requestAnimationFrame || /* Firefox 23 / IE 10 / Chrome */
			window.mozRequestAnimationFrame || /* Firefox < 23 */
			window.webkitRequestAnimationFrame || /* Safari */
			window.msRequestAnimationFrame;/* IE  */
	
	if(!window.requestAnimationFrame) {
		throw new Exception("Failed to get requestAnimationFrame funtion");
	}
	
	this.gameEntities = gameEntities;
	this.inputQueue = inputQueue;

	this.canvas = document.getElementById('canvas');
	this.context = this.canvas.getContext('2d');
	
	this.ups = -1;
	this.fps = -1;
	this.lastUpsCount = 0;
	this.lastFpsCount = 0;
	this.lastSpeedMeasureTime = new Date().getTime();
	
	this.lastLoopCallTime = 0;
	this.accumulatedTimeMs = 0;
	
	/**
	 * @public
	 */
	this.start = function() {
		this.canvas.width=600;
		this.canvas.height=300;
		this.lastLoopCallTime = this.getCurrentTimeMs();		
		/*
		 * call the update function once here. 
		 * The update function will then keep scheduling itself for subsequent calls by hooking into
		 * the requestAnimationFrame callback loop
		 */
		this.update();
	};
	
	/**
	 * @private
	 */
	this.update = function() {
		var self = this;
		
		var actualLoopDurationMs = self.getCurrentTimeMs()-self.lastLoopCallTime;
		self.lastLoopCallTime = self.getCurrentTimeMs();
		self.accumulatedTimeMs += actualLoopDurationMs;
		while(self.accumulatedTimeMs>=FIXED_STEP_IDEAL_DURATION_MS) {
			self.updateState();
			self.accumulatedTimeMs -= FIXED_STEP_IDEAL_DURATION_MS;
		}
		
		self.updateGraphics();
		
		/*
		 * request a new graphics rendering, specifying this function as the function to be called back when
		 * the browser schedules a frame update
		 */
		window.requestAnimationFrame(function() {self.update();});
	};
	
	/**
	 * @private
	 */
	this.processInput = function() {
		while(!inputQueue.isEmpty()) {
			var mouseClickEvent = inputQueue.pop();
			for(var i=0; i<this.gameEntities.length; i++) {
				var gameEntity = this.gameEntities[i];
				gameEntity.processInput(mouseClickEvent);
			}
		}
	};
	
	/**
	 * @private
	 */
	this.updateState = function() {
		this.processInput();
		
		for(var i=0; i<this.gameEntities.length; i++) {
			var gameEntity = this.gameEntities[i];
			gameEntity.updateState();
		}
		this.lastUpsCount++;
		this.displayLoopSpeed();
	};
	
	/**
	 * @private
	 */
	this.updateGraphics = function() {
		this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
		this.context.fillStyle="#C8C8C8";//light grey
		this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
		
		for(var i=0; i<this.gameEntities.length; i++) {
			var gameEntity = this.gameEntities[i];
			gameEntity.updateGraphics(this.context);
		}
		
		this.displayLoopSpeed();
		this.lastFpsCount++;
	};	
	
	/**
	 * @private
	 */
	this.getCurrentTimeMs = function() {
		return new Date().getTime();
	};
	
	/**
	 * 
	 * @private
	 */
	this.displayLoopSpeed = function() {
		var curTimeMs = this.getCurrentTimeMs();
		if(curTimeMs-this.lastSpeedMeasureTime>=500) {
			var dt = curTimeMs-this.lastSpeedMeasureTime;
			this.ups = (this.lastUpsCount*1000)/dt;
			this.fps = (this.lastFpsCount*1000)/dt;
			this.lastSpeedMeasureTime = curTimeMs;
			this.lastUpsCount=0;
			this.lastFpsCount=0;
		}
		
		this.context.fillStyle="#FF0000";
		this.context.font="12px Arial Bold";
		this.context.fillText("ups:"+Math.round(this.ups)+"   fps:"+Math.round(this.fps),0,15);
	};

};